﻿using System.Collections.Generic;
using System.Linq;

namespace InternetBankService
{
    using System;
    using System.IO;
    using System.Net.Security;
    using System.Security.Cryptography;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.Text;
    using System.Web.Security;

    using HelpersAndContracts.Contracts;
    using HelpersAndContracts.Helpers;

    using Model;
    using Helpers;

    [MessageContract(ProtectionLevel = ProtectionLevel.EncryptAndSign)]
    public class PaymentsService : IPaymentsService
    {
        private readonly IRepository repository;
        private readonly IHashClaculator hashClaculator;
        private readonly IMailSender mailSender;

        private static readonly List<PaymentOnHold> PaymentsOnHold = new List<PaymentOnHold>();

        private readonly List<CryptoModel> cryptoInformation = new List<CryptoModel>(); 

        public PaymentsService(IRepository repository, IHashClaculator hashClaculator, IMailSender mailSender )
        {
            this.repository = repository;
            this.hashClaculator = hashClaculator;
            this.mailSender = mailSender;
        }

        public string DecodeMessage(byte[] encryptedMessage)
        {
            var cryptoInfo = GetClientCryptoInfo();

            using (Aes aes = new AesCryptoServiceProvider())
            {
                aes.Key = cryptoInfo.ServerSecretKey;
                aes.IV = cryptoInfo.InitializationVector;
                // Decrypt the message 
                using (MemoryStream plaintext = new MemoryStream())
                {
                    using (CryptoStream cs = new CryptoStream(plaintext, aes.CreateDecryptor(), CryptoStreamMode.Write))
                    {
                        cs.Write(encryptedMessage, 0, encryptedMessage.Length);
                        cs.Close();
                        string message = Encoding.UTF8.GetString(plaintext.ToArray());
                        Console.WriteLine(message);
                        return message;
                    }
                }
            }
        }

        private CryptoModel GetClientCryptoInfo()
        {
            var cryptoInfo = this.cryptoInformation.FirstOrDefault(x => string.Equals(x.IPAdress, GetClientIPAdress()));
            if (cryptoInfo == null)
            {
                throw new UnauthorizedAccessException();
            }
            return cryptoInfo;
        }

        private static string GetClientIPAdress()
        {
            OperationContext context = OperationContext.Current;
            MessageProperties prop = context.IncomingMessageProperties;
            var endpoint = prop[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;
            return endpoint.Address;
        }

        public byte[] GetServerPublicKey(string username)
        {
            var cryptoModel = new CryptoModel
                {
                    IPAdress = GetClientIPAdress()
                };
            cryptoInformation.Add(cryptoModel);
            return cryptoModel.ServerPublicKey;
        }

        public void GenerateServerSecretKey(byte[] clientPublicKey, byte[] cleintInitializationVector)
        {
            var cryptoInfo = GetClientCryptoInfo();

            cryptoInfo.InitializationVector = cleintInitializationVector;
            cryptoInfo.GenerateSecreteKey(clientPublicKey);

            cryptoInformation.RemoveAll(x => x.IPAdress.Equals(cryptoInfo.IPAdress));

            cryptoInformation.Add(cryptoInfo);

        }

        public byte[] GetinitializationVector()
        {
            return GetClientCryptoInfo().Aes.IV;
        }



        public bool Authenticate(EncodedCredentialsModel credentials)
        {
            var cryptoInfo = this.GetClientCryptoInfo();
            var decodedCredentials = credentials.MapAndDecode<CredentialsModel>(cryptoInfo.ServerSecretKey, cryptoInfo.InitializationVector);
            var client = repository.GetClientByUsername(decodedCredentials.Username);
            return client != null && client.Password.Equals(hashClaculator.CalculateHash(decodedCredentials.Password));
        }



        public bool CheckTransactionPassword(EncodedCredentialsModel credentials)
        {
            var cryptoInfo = this.GetClientCryptoInfo();
            var decodedCredentials = credentials.MapAndDecode<CredentialsModel>(cryptoInfo.ServerSecretKey, cryptoInfo.InitializationVector);

            var client = repository.GetClientByUsername(decodedCredentials.Username);
            return client != null && client.TransactionPassword.Equals(hashClaculator.CalculateHash(decodedCredentials.Password));
        }

        public EncodedBalanceModel GetBalance(string username)
        {
            var cryptoInfo = this.GetClientCryptoInfo();
            var client = repository.GetClientByUsername(username);
            return new BalanceModel
            {
                Balance = client.Balance != null ? client.Balance.Value.ToString() : "0",
                CardId = client.CardId,
                Username = client.Name
            }.MapAndEncode<EncodedBalanceModel>(cryptoInfo.Aes, cryptoInfo.ServerSecretKey);
        }

        public bool ApplyPayment(EncodedPaymentModel payment)
        {
            var cryptoInfo = this.GetClientCryptoInfo();
            var decodedPayment = payment.MapAndDecode<PaymentModel>(cryptoInfo.ServerSecretKey, cryptoInfo.InitializationVector);

            var client = repository.GetClientByUsername(decodedPayment.Username);

            var paymentOnHold = new PaymentOnHold { Username = decodedPayment.Username, Number = decodedPayment.Number, Amont = decodedPayment.Amount, MCode = Membership.GeneratePassword(4, 0), Recipient = decodedPayment.Recipient };

            if (!repository.PaymentExist(decodedPayment))
            {
                PaymentsOnHold.Add(paymentOnHold);
                mailSender.SendEmail(client.Email, "Internet Bank M-код", "Ваш  M-код " + paymentOnHold.MCode);
                return false;
            }
            
            repository.AddPayment(paymentOnHold);
            return true;
        }

        public bool CheckMCodeAndSubmitPayment(EncodedCredentialsModel credentials)
        {
            var cryptoInfo = this.GetClientCryptoInfo();

            var decodedCredentials = credentials.MapAndDecode<CredentialsModel>(cryptoInfo.ServerSecretKey, cryptoInfo.InitializationVector);
            var payment = PaymentsOnHold.FirstOrDefault(x => x.Username.Equals(decodedCredentials.Username) && x.MCode.Equals(decodedCredentials.Password));
            if(payment!= null)
            {
                repository.AddPayment(payment);
                return true;
            }
            return false;
        }


        public IEnumerable<EncodedPaymentModel> GetUserPayments(string username)
        {
            var cryptoInfo = this.GetClientCryptoInfo();

            return repository.GetUserPayments(username).Select(
                    x => x.MapAndEncode<EncodedPaymentModel>(cryptoInfo.Aes, cryptoInfo.ServerSecretKey));
        }

        public EncodedClientModel AddUser(EncodedClientModel client)
        {
            var cryptoInfo = this.GetClientCryptoInfo();
            var model = client.MapAndDecode<ClientModel>(cryptoInfo.ServerSecretKey, cryptoInfo.InitializationVector);
            var result = repository.AddClient(model);
            return result.MapAndEncode<EncodedClientModel>(cryptoInfo.Aes, cryptoInfo.ServerSecretKey);
        }


    }
}
